updating some changes i forgot to commit
This commit is contained in:
		
							parent
							
								
									5e4c56af29
								
							
						
					
					
						commit
						b08aeee4fc
					
				
							
								
								
									
										13
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								gulpfile.js
									
									
									
									
									
								
							@ -28,14 +28,17 @@
 | 
				
			|||||||
    Specify which specs to use!
 | 
					    Specify which specs to use!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Commands:
 | 
					  Commands:
 | 
				
			||||||
    - build:
 | 
					    - build
 | 
				
			||||||
        Build this library
 | 
					        Build this library
 | 
				
			||||||
    - dev:
 | 
					    - dev:browser
 | 
				
			||||||
 | 
					        Watch the ./src directory.
 | 
				
			||||||
 | 
					        Builds the library on changes.
 | 
				
			||||||
 | 
					        Starts an http-server and serves the test suite on http://127.0.0.1:8888.
 | 
				
			||||||
 | 
					    - dev:node
 | 
				
			||||||
        Watch the ./src directory.
 | 
					        Watch the ./src directory.
 | 
				
			||||||
        Builds and specs the library on changes.
 | 
					        Builds and specs the library on changes.
 | 
				
			||||||
        Starts an http-server and serves the test suite on http://127.0.0.1:8888.
 | 
					        Usefull to run with node-inspector.
 | 
				
			||||||
    - build_test:
 | 
					        `node-debug $(which gulp) dev:node
 | 
				
			||||||
        Builds the test suite
 | 
					 | 
				
			||||||
    - test:
 | 
					    - test:
 | 
				
			||||||
        Test this library
 | 
					        Test this library
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
				
			|||||||
@ -142,27 +142,35 @@ class AbstractConnector {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.debug) {
 | 
					    if (this.debug) {
 | 
				
			||||||
      console.log(`${sender} -> me: ${m.type}`, m);// eslint-disable-line
 | 
					      console.log(`${sender} -> me: ${m.type}`, m);// eslint-disable-line
 | 
				
			||||||
 | 
					      if (m.os != null && m.os.some(function(o){return o.deleted })){
 | 
				
			||||||
 | 
					        console.log("bullshit.. ")
 | 
				
			||||||
 | 
					        debugger
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (m.type === 'sync step 1') {
 | 
					    if (m.type === 'sync step 1') {
 | 
				
			||||||
      // TODO: make transaction, stream the ops
 | 
					      // TODO: make transaction, stream the ops
 | 
				
			||||||
      let conn = this
 | 
					      let conn = this
 | 
				
			||||||
      this.y.db.requestTransaction(function *() {
 | 
					      this.y.db.requestTransaction(function *() {
 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateSet)
 | 
					        var ops = yield* this.getOperations(m.stateSet)
 | 
				
			||||||
        var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
 | 
					 | 
				
			||||||
        if (dels.length > 0) {
 | 
					 | 
				
			||||||
          this.store.apply(dels)
 | 
					 | 
				
			||||||
          // broadcast missing dels from syncing client
 | 
					 | 
				
			||||||
          this.store.y.connector.broadcast({
 | 
					 | 
				
			||||||
            type: 'update',
 | 
					 | 
				
			||||||
            ops: dels
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        conn.send(sender, {
 | 
					        conn.send(sender, {
 | 
				
			||||||
          type: 'sync step 2',
 | 
					          type: 'sync step 2',
 | 
				
			||||||
          os: ops,
 | 
					          os: ops,
 | 
				
			||||||
          stateSet: yield* this.getStateSet(),
 | 
					          stateSet: yield* this.getStateSet(),
 | 
				
			||||||
          deleteSet: yield* this.getDeleteSet()
 | 
					          deleteSet: yield* this.getDeleteSet() // TODO: consider that you have a ds from the other user..
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
 | 
				
			||||||
 | 
					        if (dels.length > 0) {
 | 
				
			||||||
 | 
					          for (var i in dels) {
 | 
				
			||||||
 | 
					            // TODO: no longer get delete ops (just get the ids..)!
 | 
				
			||||||
 | 
					            yield* Y.Struct.Delete.delete.call(this, dels[i].target)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          /*/ broadcast missing dels from syncing client
 | 
				
			||||||
 | 
					          this.store.y.connector.broadcast({
 | 
				
			||||||
 | 
					            type: 'update',
 | 
				
			||||||
 | 
					            ops: dels
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          */
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (this.forwardToSyncingClients) {
 | 
					        if (this.forwardToSyncingClients) {
 | 
				
			||||||
          conn.syncingClients.push(sender)
 | 
					          conn.syncingClients.push(sender)
 | 
				
			||||||
          setTimeout(function () {
 | 
					          setTimeout(function () {
 | 
				
			||||||
@ -187,14 +195,14 @@ class AbstractConnector {
 | 
				
			|||||||
      this.y.db.requestTransaction(function *() {
 | 
					      this.y.db.requestTransaction(function *() {
 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateSet)
 | 
					        var ops = yield* this.getOperations(m.stateSet)
 | 
				
			||||||
        var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
 | 
					        var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
 | 
				
			||||||
        this.store.apply(dels)
 | 
					 | 
				
			||||||
        this.store.apply(m.os)
 | 
					        this.store.apply(m.os)
 | 
				
			||||||
 | 
					        this.store.apply(dels)
 | 
				
			||||||
        if (ops.length > 0) {
 | 
					        if (ops.length > 0) {
 | 
				
			||||||
          m = {
 | 
					          m = {
 | 
				
			||||||
            type: 'update',
 | 
					            type: 'update',
 | 
				
			||||||
            ops: ops
 | 
					            ops: ops
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (!broadcastHB) {
 | 
					          if (!broadcastHB || true) { // TODO: consider to broadcast here..
 | 
				
			||||||
            conn.send(sender, m)
 | 
					            conn.send(sender, m)
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            // broadcast only once!
 | 
					            // broadcast only once!
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,9 @@ class Test extends Y.AbstractConnector {
 | 
				
			|||||||
    globalRoom.addUser(this)
 | 
					    globalRoom.addUser(this)
 | 
				
			||||||
    this.globalRoom = globalRoom
 | 
					    this.globalRoom = globalRoom
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  receiveMessage (sender, m) {
 | 
				
			||||||
 | 
					    super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  send (userId, message) {
 | 
					  send (userId, message) {
 | 
				
			||||||
    globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
					    globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -84,16 +84,15 @@ g.applyRandomTransactions = async(function * applyRandomTransactions (users, obj
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  applyTransactions()
 | 
					  applyTransactions()
 | 
				
			||||||
  applyTransactions()
 | 
					 | 
				
			||||||
  /* TODO: call applyTransactions here..
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					  yield users[0].connector.flushAll()
 | 
				
			||||||
 | 
					  yield g.garbageCollectAllUsers(users)
 | 
				
			||||||
  users[0].disconnect()
 | 
					  users[0].disconnect()
 | 
				
			||||||
  yield wait()
 | 
					  yield wait()
 | 
				
			||||||
  applyTransactions()
 | 
					  applyTransactions()
 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					  yield users[0].connector.flushAll()
 | 
				
			||||||
 | 
					  yield g.garbageCollectAllUsers(users)
 | 
				
			||||||
  users[0].reconnect()
 | 
					  users[0].reconnect()
 | 
				
			||||||
  */
 | 
					  yield wait(100)
 | 
				
			||||||
  yield wait()
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					  yield users[0].connector.flushAll()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -104,7 +103,7 @@ g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-line
 | 
					g.compareAllUsers = async(function * compareAllUsers (users) {
 | 
				
			||||||
  var s1, s2 // state sets
 | 
					  var s1, s2 // state sets
 | 
				
			||||||
  var ds1, ds2 // delete sets
 | 
					  var ds1, ds2 // delete sets
 | 
				
			||||||
  var allDels1, allDels2 // all deletions
 | 
					  var allDels1, allDels2 // all deletions
 | 
				
			||||||
@ -129,11 +128,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					  yield users[0].connector.flushAll()
 | 
				
			||||||
  // gc two times because of the two gc phases (really collect everything)
 | 
					  // gc two times because of the two gc phases (really collect everything)
 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					  yield g.garbageCollectAllUsers(users)
 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (var uid = 0; uid < users.length; uid++) {
 | 
					  for (var uid = 0; uid < users.length; uid++) {
 | 
				
			||||||
    var u = users[uid]
 | 
					    var u = users[uid]
 | 
				
			||||||
 | 
				
			|||||||
@ -122,7 +122,7 @@ Y.AbstractTransaction = AbstractTransaction
 | 
				
			|||||||
  * destroy()
 | 
					  * destroy()
 | 
				
			||||||
    - destroy the database
 | 
					    - destroy the database
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class AbstractOperationStore { // eslint-disable-line no-unused-vars
 | 
					class AbstractOperationStore {
 | 
				
			||||||
  constructor (y, opts) {
 | 
					  constructor (y, opts) {
 | 
				
			||||||
    this.y = y
 | 
					    this.y = y
 | 
				
			||||||
    // E.g. this.listenersById[id] : Array<Listener>
 | 
					    // E.g. this.listenersById[id] : Array<Listener>
 | 
				
			||||||
@ -239,7 +239,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
 | 
				
			|||||||
  apply (ops) {
 | 
					  apply (ops) {
 | 
				
			||||||
    for (var key in ops) {
 | 
					    for (var key in ops) {
 | 
				
			||||||
      var o = ops[key]
 | 
					      var o = ops[key]
 | 
				
			||||||
      if (!o.gc) {
 | 
					      if (o.gc == null) { // TODO: why do i get the same op twice?
 | 
				
			||||||
        var required = Y.Struct[o.struct].requiredOps(o)
 | 
					        var required = Y.Struct[o.struct].requiredOps(o)
 | 
				
			||||||
        this.whenOperationsExist(required, o)
 | 
					        this.whenOperationsExist(required, o)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@ -316,17 +316,19 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      while (op != null) {
 | 
					      while (op != null) {
 | 
				
			||||||
        var state = yield* this.getState(op.id[0])
 | 
					        var state = yield* this.getState(op.id[0])
 | 
				
			||||||
 | 
					        if (op.id[1] === state.clock || (op.id[1] < state.clock && (yield* this.getOperation(op.id)) == null)) {
 | 
				
			||||||
 | 
					          // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
 | 
				
			||||||
          if (op.id[1] === state.clock) {
 | 
					          if (op.id[1] === state.clock) {
 | 
				
			||||||
            state.clock++
 | 
					            state.clock++
 | 
				
			||||||
            yield* this.checkDeleteStoreForState(state)
 | 
					            yield* this.checkDeleteStoreForState(state)
 | 
				
			||||||
            yield* this.setState(state)
 | 
					            yield* this.setState(state)
 | 
				
			||||||
          var isDeleted = this.store.ds.isDeleted(op.id)
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          yield* Y.Struct[op.struct].execute.call(this, op)
 | 
					          yield* Y.Struct[op.struct].execute.call(this, op)
 | 
				
			||||||
          yield* this.addOperation(op)
 | 
					          yield* this.addOperation(op)
 | 
				
			||||||
          yield* this.store.operationAdded(this, op)
 | 
					          yield* this.store.operationAdded(this, op)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (isDeleted) {
 | 
					          if (this.store.ds.isDeleted(op.id)) {
 | 
				
			||||||
            yield* Y.Struct['Delete'].execute.call(this, {struct: 'Delete', target: op.id})
 | 
					            yield* Y.Struct['Delete'].execute.call(this, {struct: 'Delete', target: op.id})
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -193,17 +193,15 @@ Y.Memory = (function () {
 | 
				
			|||||||
        var startPos = startSS[user] || 0
 | 
					        var startPos = startSS[user] || 0
 | 
				
			||||||
        var endPos = endState.clock
 | 
					        var endPos = endState.clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line
 | 
					        this.os.iterate([user, startPos], [user, endPos], function (op) {
 | 
				
			||||||
          if (!op.gc) {
 | 
					          ops.push(op)
 | 
				
			||||||
            ops.push(Y.Struct[op.struct].encode(op))
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var res = []
 | 
					      var res = []
 | 
				
			||||||
      for (var op of ops) {
 | 
					      for (var op of ops) {
 | 
				
			||||||
        res.push(yield* this.makeOperationReady(startSS, op))
 | 
					        res.push(yield* this.makeOperationReady(startSS, op))
 | 
				
			||||||
        var state = startSS[op.id[0]] || 0
 | 
					        var state = startSS[op.id[0]] || 0
 | 
				
			||||||
        if (state === op.id[1] || true) {
 | 
					        if ((state === op.id[1]) || true) {
 | 
				
			||||||
          startSS[op.id[0]] = state + 1
 | 
					          startSS[op.id[0]] = state + 1
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          throw new Error('Unexpected operation!')
 | 
					          throw new Error('Unexpected operation!')
 | 
				
			||||||
@ -212,32 +210,32 @@ Y.Memory = (function () {
 | 
				
			|||||||
      return res
 | 
					      return res
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    * makeOperationReady (ss, op) {
 | 
					    * makeOperationReady (ss, op) {
 | 
				
			||||||
 | 
					      op = Y.Struct[op.struct].encode(op)
 | 
				
			||||||
      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
					      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
				
			||||||
      op = Y.utils.copyObject(op)
 | 
					      op = Y.utils.copyObject(op)
 | 
				
			||||||
      var o = op
 | 
					      var o = op
 | 
				
			||||||
      var clock
 | 
					 | 
				
			||||||
      while (o.right != null) {
 | 
					      while (o.right != null) {
 | 
				
			||||||
        // while unknown, go to the right
 | 
					        // while unknown, go to the right
 | 
				
			||||||
        clock = ss[o.right[0]] || 0
 | 
					        if (o.right[1] < (ss[o.right[0]] || 0)) {
 | 
				
			||||||
        if (o.right[1] < clock && !o.gc) {
 | 
					 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        o = yield* this.getOperation(o.right)
 | 
					        o = yield* this.getOperation(o.right)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      // new right is not gc'd and known according to the ss
 | 
				
			||||||
      op.right = o.right
 | 
					      op.right = o.right
 | 
				
			||||||
      while (o.left != null) {
 | 
					      while (o.left != null) {
 | 
				
			||||||
        // while unknown, go to the right
 | 
					        // while unknown, go to the right
 | 
				
			||||||
        clock = ss[o.left[0]] || 0
 | 
					        if (o.left[1] < (ss[o.left[0]] || 0)) {
 | 
				
			||||||
        if (o.left[1] < clock && !o.gc) {
 | 
					 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        o = yield* this.getOperation(o.left)
 | 
					        o = yield* this.getOperation(o.left)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      // new left is not gc'd and known according to the ss
 | 
				
			||||||
      op.left = o.left
 | 
					      op.left = o.left
 | 
				
			||||||
      return op
 | 
					      return op
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  class OperationStore extends Y.AbstractOperationStore { // eslint-disable-line no-undef
 | 
					  class OperationStore extends Y.AbstractOperationStore {
 | 
				
			||||||
    constructor (y, opts) {
 | 
					    constructor (y, opts) {
 | 
				
			||||||
      super(y, opts)
 | 
					      super(y, opts)
 | 
				
			||||||
      this.os = new Y.utils.RBTree()
 | 
					      this.os = new Y.utils.RBTree()
 | 
				
			||||||
 | 
				
			|||||||
@ -194,17 +194,27 @@ class RBTree {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return true
 | 
					    return true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  logTable (from, to) {
 | 
					  logTable (from, to, filter) {
 | 
				
			||||||
 | 
					    if (filter == null) {
 | 
				
			||||||
 | 
					      filter = function () {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (from == null) { from = null }
 | 
					    if (from == null) { from = null }
 | 
				
			||||||
    if (to == null) { to = null }
 | 
					    if (to == null) { to = null }
 | 
				
			||||||
    var os = []
 | 
					    var os = []
 | 
				
			||||||
    this.iterate(from, to, function (o) {
 | 
					    this.iterate(from, to, function (o) {
 | 
				
			||||||
      var o_ = Y.utils.copyObject(o)
 | 
					      if (filter(o)) {
 | 
				
			||||||
      var id = o_.id
 | 
					        var o_ = {}
 | 
				
			||||||
      delete o_.id
 | 
					        for (var key in o) {
 | 
				
			||||||
      o_['id[0]'] = id[0]
 | 
					          if (typeof o[key] === 'object') {
 | 
				
			||||||
      o_['id[1]'] = id[1]
 | 
					            o_[key] = JSON.stringify(o[key])
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            o_[key] = o[key]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        os.push(o_)
 | 
					        os.push(o_)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    if (console.table != null) {
 | 
					    if (console.table != null) {
 | 
				
			||||||
      console.table(os)
 | 
					      console.table(os)
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,7 @@ var Struct = {
 | 
				
			|||||||
      return op
 | 
					      return op
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function (op) {
 | 
					    requiredOps: function (op) {
 | 
				
			||||||
      return [op.target]
 | 
					      return [] // [op.target]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
      Delete an operation from the OS, and add it to the GC, if necessary.
 | 
					      Delete an operation from the OS, and add it to the GC, if necessary.
 | 
				
			||||||
 | 
				
			|||||||
@ -8,10 +8,10 @@ describe('Array Type', function () {
 | 
				
			|||||||
  var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
 | 
					  var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(async(function * (done) {
 | 
					  beforeEach(async(function * (done) {
 | 
				
			||||||
    yield createUsers(this, 3)
 | 
					    yield createUsers(this, 2)
 | 
				
			||||||
    y1 = (yconfig1 = this.users[0]).root
 | 
					    y1 = (yconfig1 = this.users[0]).root
 | 
				
			||||||
    y2 = (yconfig2 = this.users[1]).root
 | 
					    y2 = (yconfig2 = this.users[1]).root
 | 
				
			||||||
    y3 = (yconfig3 = this.users[2]).root
 | 
					    // y3 = (yconfig3 = this.users[2]).root
 | 
				
			||||||
    flushAll = this.users[0].connector.flushAll
 | 
					    flushAll = this.users[0].connector.flushAll
 | 
				
			||||||
    yield wait(10)
 | 
					    yield wait(10)
 | 
				
			||||||
    done()
 | 
					    done()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user