Merge pull request #2532 from Bilb/fix-deleted-messages-all-at-once
To merge once theming is done: handle deleted messages & deleted reacts all at oncepull/2598/head
commit
32e00227a4
@ -0,0 +1,48 @@
|
||||
import { RingBuffer } from '../../../utils/RingBuffer';
|
||||
|
||||
const rollingDeletedMessageIds: Map<string, RingBuffer<number>> = new Map();
|
||||
|
||||
const addMessageDeletedId = (conversationId: string, messageDeletedId: number) => {
|
||||
if (!rollingDeletedMessageIds.has(conversationId)) {
|
||||
rollingDeletedMessageIds.set(
|
||||
conversationId,
|
||||
new RingBuffer<number>(sogsRollingDeletions.getPerRoomCount())
|
||||
);
|
||||
}
|
||||
const ringBuffer = rollingDeletedMessageIds.get(conversationId);
|
||||
if (!ringBuffer) {
|
||||
return;
|
||||
}
|
||||
ringBuffer.insert(messageDeletedId);
|
||||
};
|
||||
|
||||
const hasMessageDeletedId = (conversationId: string, messageDeletedId: number) => {
|
||||
if (!rollingDeletedMessageIds.has(conversationId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageIdWasDeletedRecently = rollingDeletedMessageIds
|
||||
?.get(conversationId)
|
||||
?.has(messageDeletedId);
|
||||
|
||||
return messageIdWasDeletedRecently;
|
||||
};
|
||||
|
||||
/**
|
||||
* emptyMessageDeleteIds should only be used for testing purposes.
|
||||
*/
|
||||
const emptyMessageDeleteIds = () => {
|
||||
rollingDeletedMessageIds.clear();
|
||||
};
|
||||
|
||||
export const sogsRollingDeletions = {
|
||||
addMessageDeletedId,
|
||||
hasMessageDeletedId,
|
||||
emptyMessageDeleteIds,
|
||||
getPerRoomCount,
|
||||
};
|
||||
|
||||
// keep 2000 deleted message ids in memory
|
||||
function getPerRoomCount() {
|
||||
return 2000;
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* This ringbuffer class can be used to keep a list of at most a size and removing old items first when the size is exceeded.
|
||||
* Internally, it uses an array to keep track of the order, so two times the same item can exist in it.
|
||||
*
|
||||
*/
|
||||
export class RingBuffer<T> {
|
||||
private newest = -1;
|
||||
private oldest = 0;
|
||||
private buffer: Array<T> = [];
|
||||
private readonly capacity: number;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public getCapacity(): number {
|
||||
return this.capacity;
|
||||
}
|
||||
|
||||
public getLength(): number {
|
||||
if (this.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// When only one item was added, newest = 0 and oldest = 0.
|
||||
// When more than one item was added, but less than capacity, newest = nbItemsAdded & oldest = 0.
|
||||
// As soon as we overflow, oldest is incremented to oldest+1 and newest rolls back to 0,
|
||||
// so this test fails here and we have to extract the length based on the two parts instead.
|
||||
if (this.newest >= this.oldest) {
|
||||
return this.newest + 1;
|
||||
}
|
||||
const firstPart = this.capacity - this.oldest;
|
||||
const secondPart = this.newest + 1;
|
||||
return firstPart + secondPart;
|
||||
}
|
||||
|
||||
public insert(item: T) {
|
||||
// see comments in `getLength()`
|
||||
this.newest = (this.newest + 1) % this.capacity;
|
||||
if (this.buffer.length >= this.capacity) {
|
||||
this.oldest = (this.oldest + 1) % this.capacity;
|
||||
}
|
||||
this.buffer[this.newest] = item;
|
||||
}
|
||||
|
||||
public has(item: T) {
|
||||
// no items at all
|
||||
if (this.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return this.toArray().includes(item);
|
||||
}
|
||||
|
||||
public isEmpty() {
|
||||
return this.newest === -1;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.buffer = [];
|
||||
this.newest = -1;
|
||||
this.oldest = 0;
|
||||
}
|
||||
|
||||
public toArray(): Array<T> {
|
||||
if (this.isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.newest >= this.oldest) {
|
||||
return this.buffer.slice(0, this.newest + 1);
|
||||
}
|
||||
const firstPart = this.buffer.slice(this.oldest, this.capacity);
|
||||
const secondPart = this.buffer.slice(0, this.newest + 1);
|
||||
return [...firstPart, ...secondPart];
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { expect } from 'chai';
|
||||
import Sinon from 'sinon';
|
||||
import { sogsRollingDeletions } from '../../../../session/apis/open_group_api/sogsv3/sogsRollingDeletions';
|
||||
|
||||
describe('sogsRollingDeletions', () => {
|
||||
beforeEach(() => {
|
||||
sogsRollingDeletions.emptyMessageDeleteIds();
|
||||
Sinon.stub(sogsRollingDeletions, 'getPerRoomCount').returns(5);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Sinon.restore();
|
||||
});
|
||||
|
||||
it('no items at all returns false', () => {
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 1)).to.be.equal(
|
||||
false,
|
||||
'1 should not be there'
|
||||
);
|
||||
});
|
||||
|
||||
it('no items in that convo returns false', () => {
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 1);
|
||||
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo2', 1)).to.be.equal(
|
||||
false,
|
||||
'1 should not be there'
|
||||
);
|
||||
});
|
||||
|
||||
it('can add 1 item', () => {
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 1);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 1)).to.be.equal(
|
||||
true,
|
||||
'1 should be there'
|
||||
);
|
||||
});
|
||||
|
||||
it('can add more than capacity items', () => {
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 1);
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 2);
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 3);
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 4);
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 5);
|
||||
sogsRollingDeletions.addMessageDeletedId('convo1', 6);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 1)).to.be.equal(
|
||||
false,
|
||||
'1 should not be there'
|
||||
);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 2)).to.be.equal(
|
||||
true,
|
||||
'2 should be there'
|
||||
);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 3)).to.be.equal(
|
||||
true,
|
||||
'3 should be there'
|
||||
);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 4)).to.be.equal(
|
||||
true,
|
||||
'4 should be there'
|
||||
);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 5)).to.be.equal(
|
||||
true,
|
||||
'5 should be there'
|
||||
);
|
||||
expect(sogsRollingDeletions.hasMessageDeletedId('convo1', 6)).to.be.equal(
|
||||
true,
|
||||
'6 should be there'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,224 @@
|
||||
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression no-require-imports no-var-requires
|
||||
|
||||
import chai from 'chai';
|
||||
import { RingBuffer } from '../../../../session/utils/RingBuffer';
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('RingBuffer Utils', () => {
|
||||
it('gets created with right capacity', () => {
|
||||
const ring = new RingBuffer<number>(5000);
|
||||
expect(ring.getCapacity()).to.equal(5000);
|
||||
expect(ring.getLength()).to.equal(0);
|
||||
expect(ring.has(0)).to.equal(false, '0 should not be there');
|
||||
});
|
||||
|
||||
describe('length & capacity are right', () => {
|
||||
it('length is right 0', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
expect(ring.getLength()).to.equal(0);
|
||||
});
|
||||
|
||||
it('length is right 1', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
expect(ring.getLength()).to.equal(1);
|
||||
});
|
||||
|
||||
it('length is right 4', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
expect(ring.getLength()).to.equal(4);
|
||||
});
|
||||
|
||||
it('capacity does not get exceeded', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4);
|
||||
expect(ring.getLength()).to.equal(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEmpty is correct', () => {
|
||||
it('no items', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
expect(ring.isEmpty()).to.equal(true, 'no items isEmpty should be true');
|
||||
});
|
||||
|
||||
it('length is right 1', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
expect(ring.isEmpty()).to.equal(false, '1 item isEmpty should be false');
|
||||
});
|
||||
|
||||
it('length is right 4', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
expect(ring.isEmpty()).to.equal(false, '4 items isEmpty should be false');
|
||||
});
|
||||
|
||||
it('more than capacity', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4);
|
||||
expect(ring.isEmpty()).to.equal(false, '5 item isEmpty should be false');
|
||||
});
|
||||
});
|
||||
|
||||
it('items are removed in order 1', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4);
|
||||
expect(ring.has(0)).to.equal(false, '0 should not be there anymore');
|
||||
expect(ring.has(1)).to.equal(true, '1 should still be there');
|
||||
expect(ring.has(2)).to.equal(true, '2 should still be there');
|
||||
expect(ring.has(3)).to.equal(true, '3 should still be there');
|
||||
expect(ring.has(4)).to.equal(true, '4 should still be there');
|
||||
});
|
||||
|
||||
it('two times the same items can exist', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(1);
|
||||
ring.insert(4);
|
||||
expect(ring.has(0)).to.equal(false, '0 should not be there anymore');
|
||||
expect(ring.has(1)).to.equal(true, '1 should still be there');
|
||||
expect(ring.has(2)).to.equal(true, '2 should still be there');
|
||||
expect(ring.has(3)).to.equal(false, '3 should not be there');
|
||||
expect(ring.has(4)).to.equal(true, '4 should still be there');
|
||||
});
|
||||
|
||||
it('items are removed in order completely', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(10);
|
||||
ring.insert(20);
|
||||
ring.insert(30);
|
||||
ring.insert(40);
|
||||
expect(ring.has(0)).to.equal(false, '0 should not be there anymore');
|
||||
expect(ring.has(1)).to.equal(false, '1 should not be there');
|
||||
expect(ring.has(2)).to.equal(false, '2 should not be there');
|
||||
expect(ring.has(3)).to.equal(false, '3 should not be there');
|
||||
expect(ring.has(4)).to.equal(false, '4 should not be there');
|
||||
|
||||
expect(ring.has(10)).to.equal(true, '10 should still be there');
|
||||
expect(ring.has(20)).to.equal(true, '20 should still be there');
|
||||
expect(ring.has(30)).to.equal(true, '30 should still be there');
|
||||
expect(ring.has(40)).to.equal(true, '40 should still be there');
|
||||
});
|
||||
|
||||
it('clear empties the list but keeps the capacity', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(1);
|
||||
expect(ring.getLength()).to.equal(4);
|
||||
expect(ring.getCapacity()).to.equal(4);
|
||||
ring.clear();
|
||||
expect(ring.getCapacity()).to.equal(4);
|
||||
|
||||
expect(ring.getLength()).to.equal(0);
|
||||
});
|
||||
|
||||
describe('toArray', () => {
|
||||
it('empty buffer', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
expect(ring.toArray()).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('with 1', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
|
||||
expect(ring.toArray()).to.deep.eq([0]);
|
||||
});
|
||||
|
||||
it('with 4', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
|
||||
expect(ring.toArray()).to.deep.eq([0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
it('with 5', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4);
|
||||
|
||||
expect(ring.toArray()).to.deep.eq([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('more than 2 full laps erasing data', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4); // first lap first item
|
||||
ring.insert(5);
|
||||
ring.insert(6); // first item in toArray should be this one
|
||||
ring.insert(7);
|
||||
ring.insert(8); // second lap first item
|
||||
ring.insert(9);
|
||||
|
||||
expect(ring.toArray()).to.deep.eq([6, 7, 8, 9]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('empty buffer', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.clear();
|
||||
expect(ring.getCapacity()).to.deep.eq(4);
|
||||
expect(ring.getLength()).to.deep.eq(0);
|
||||
});
|
||||
|
||||
it('with 1', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.clear();
|
||||
expect(ring.getCapacity()).to.deep.eq(4);
|
||||
expect(ring.getLength()).to.deep.eq(0);
|
||||
});
|
||||
|
||||
it('with 5', () => {
|
||||
const ring = new RingBuffer<number>(4);
|
||||
ring.insert(0);
|
||||
ring.insert(1);
|
||||
ring.insert(2);
|
||||
ring.insert(3);
|
||||
ring.insert(4);
|
||||
|
||||
ring.clear();
|
||||
expect(ring.getCapacity()).to.deep.eq(4);
|
||||
expect(ring.getLength()).to.deep.eq(0);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue