bool Writer::writeMessage(const protocol::Message &msg) { Q_ASSERT(m_file->isOpen()); if(m_encoding == Encoding::Binary) { QVarLengthArray<char> buf(msg.length()); const int len = msg.serialize(buf.data()); Q_ASSERT(len == buf.length()); if(m_file->write(buf.data(), len) != len) return false; } else { if(msg.type() == protocol::MSG_FILTERED) { // Special case: Filtered messages are // written as comments in the text format. const protocol::Filtered &fm = static_cast<const protocol::Filtered&>(msg); auto wrapped = fm.decodeWrapped(); QString comment; if(wrapped.isNull()) { comment = QStringLiteral("FILTERED: undecodable message type #%1 of length %2") .arg(fm.wrappedType()) .arg(fm.wrappedPayloadLength()); } else { comment = QStringLiteral("FILTERED: ") + wrapped->toString(); } return writeComment(comment); } QByteArray line = msg.toString().toUtf8(); if(m_file->write(line) != line.length()) return false; if(m_file->write("\n", 1) != 1) return false; // Write extra newlines after certain commands to give // the file some visual structure switch(msg.type()) { case protocol::MSG_UNDOPOINT: if(m_file->write("\n", 1) != 1) return false; default: break; } } return true; }
void Writer::recordMessage(const protocol::Message &msg) { Q_ASSERT(_file->isOpen()); if(!_filterMeta || msg.isCommand() || isRecordableMeta(msg.type())) { // Write Interval message if sufficient time has passed since last message was written if(_minInterval>0) { qint64 now = QDateTime::currentMSecsSinceEpoch(); qint64 interval = now - _interval; if(interval >= _minInterval) { protocol::Interval imsg(qMin(qint64(0xffff), interval)); QVarLengthArray<char> ibuf(imsg.length()); int ilen = imsg.serialize(ibuf.data()); _file->write(ibuf.data(), ilen); } _interval = now; } // Write the actual message QVarLengthArray<char> buf(msg.length()); int len = msg.serialize(buf.data()); Q_ASSERT(len == buf.length()); _file->write(buf.data(), len); } }
/** * @brief Draw brush dabs onto a specific layer * * Typically, you should call drawBrushDabs instead. However, this function * must be called directly when you're drawing onto a preview sublayer. * * @param msg brush dab message * @param layer the layer to draw onto */ void drawBrushDabsDirect(const protocol::Message &msg, paintcore::EditableLayer layer, int sublayer) { Q_ASSERT(!layer.isNull()); switch(msg.type()) { case protocol::MSG_DRAWDABS_CLASSIC: drawClassicBrushDabs(static_cast<const protocol::DrawDabsClassic&>(msg), layer, sublayer); break; case protocol::MSG_DRAWDABS_PIXEL: case protocol::MSG_DRAWDABS_PIXEL_SQUARE: drawPixelBrushDabs(static_cast<const protocol::DrawDabsPixel&>(msg), layer, sublayer); break; default: qWarning("Unhandled dab type: %s", qPrintable(msg.messageName())); } }
bool AclFilter::filterMessage(const protocol::Message &msg) { using namespace protocol; // Session and user specific locks apply to all Command type messages if(msg.isCommand() && (m_sessionLocked || m_userlocks.contains(msg.contextId()))) return false; // This user's access level tier determines which features are available const Tier tier = userTier(msg.contextId()); switch(msg.type()) { case MSG_USER_JOIN: if((static_cast<const UserJoin&>(msg).flags() & UserJoin::FLAG_AUTH)) m_auth.set(msg.contextId()); // Make sure the user's access bits are up to date emit operatorListChanged(m_ops.toList()); emit trustedUserListChanged(m_trusted.toList()); if(msg.contextId() == m_myId) { for(int i=0;i<FeatureCount;++i) emit featureAccessChanged(Feature(i), canUseFeature(Feature(i))); } break; case MSG_USER_LEAVE: { // User left: remove locks m_ops.unset(msg.contextId()); m_trusted.unset(msg.contextId()); m_auth.unset(msg.contextId()); m_userlocks.unset(msg.contextId()); QMutableHashIterator<int,LayerAcl> i(m_layers); while(i.hasNext()) { i.next(); if(i.value().exclusive.removeAll(msg.contextId())>0) emit layerAclChanged(i.key()); } // Refresh UI if(msg.contextId() == m_myId) { setOperator(false); setTrusted(false); m_localUserLocked = false; emit localLockChanged(isLocked()); } break; } case MSG_SESSION_OWNER: // This command is validated by the server updateSessionOwnership(static_cast<const SessionOwner&>(msg)); return true; case MSG_TRUSTED_USERS: // this command is validated by the server updateTrustedUserList(static_cast<const TrustedUsers&>(msg)); return true; case MSG_LAYER_ACL: if( tier <= featureTier(Feature::EditLayers) || (tier <= featureTier(Feature::OwnLayers) && layerCreator(msg.layer()) == msg.contextId()) ) { const auto &lmsg = static_cast<const LayerACL&>(msg); if(lmsg.layer() == 0) { // Layer 0 sets the general session lock. // Exclusive user list is not used in this case. if(tier > Tier::Op) return false; setSessionLock(lmsg.locked()); return true; } const Tier tier = Tier(qBound(0, int(lmsg.tier()), TierCount)); m_layers[lmsg.layer()] = LayerAcl { lmsg.locked(), tier, lmsg.exclusive() }; // Emit this to refresh the UI in case our selected layer was (un)locked. // (We don't actually know which layer is selected in the UI here.) emit localLockChanged(isLocked()); emit layerAclChanged(lmsg.layer()); return true; } return false; case MSG_FEATURE_LEVELS: { if(tier > Tier::Op) return false; const auto &flmsg = static_cast<const FeatureAccessLevels&>(msg); for(int i=0;i<canvas::FeatureCount;++i) { setFeature(Feature(i), Tier(qBound(0, int(flmsg.featureTier(i)), canvas::TierCount))); } return true; } case MSG_USER_ACL: { if(tier > Tier::Op) return false; const auto &lmsg = static_cast<const UserACL&>(msg); m_userlocks.setFromList(lmsg.ids()); emit userLocksChanged(lmsg.ids()); setUserLock(m_userlocks.contains(m_myId)); return true; } case MSG_LAYER_DEFAULT: return tier == Tier::Op; case MSG_CHAT: // Only operators can pin messages if(static_cast<const protocol::Chat&>(msg).isPin() && tier > Tier::Op) return false; break; case MSG_LASERTRAIL: return tier <= featureTier(Feature::Laser); case MSG_CANVAS_RESIZE: return tier <= featureTier(Feature::Resize); case MSG_PUTTILE: return tier == Tier::Op; case MSG_CANVAS_BACKGROUND: return tier <= featureTier(Feature::Background); case MSG_LAYER_CREATE: { if(tier > Tier::Op && layerCreator(msg.layer()) != msg.contextId()) { qWarning("non-op user %d tried to create layer with context id %d", msg.contextId(), layerCreator(msg.layer())); return false; } // Must have either general or ownlayer permission to create layers return tier <= featureTier(Feature::EditLayers) || tier <= featureTier(Feature::OwnLayers); } case MSG_LAYER_ATTR: if(static_cast<const protocol::LayerAttributes&>(msg).sublayer()>0 && tier > Tier::Op) { // Direct sublayer editing is used only by operators during session init return false; } Q_FALLTHROUGH(); case MSG_LAYER_RETITLE: case MSG_LAYER_DELETE: { const uint8_t createdBy = layerCreator(msg.layer()); // EDITLAYERS feature gives permission to edit all layers // OWNLAYERS feature gives permission to edit layers created by this user if( (createdBy != msg.contextId() && tier > featureTier(Feature::EditLayers)) || (createdBy == msg.contextId() && tier > featureTier(Feature::OwnLayers)) ) return false; if(msg.type() == MSG_LAYER_DELETE) m_layers.remove(msg.layer()); break; } case MSG_LAYER_ORDER: return tier <= featureTier(Feature::EditLayers); case MSG_PUTIMAGE: case MSG_FILLRECT: return tier <= featureTier(Feature::PutImage) && !isLayerLockedFor(msg.layer(), msg.contextId(), tier); case MSG_DRAWDABS_CLASSIC: case MSG_DRAWDABS_PIXEL: return !isLayerLockedFor(msg.layer(), msg.contextId(), tier); case MSG_REGION_MOVE: return tier <= featureTier(Feature::RegionMove) && !isLayerLockedFor(msg.layer(), msg.contextId(), tier); case MSG_ANNOTATION_CREATE: if(tier > featureTier(Feature::CreateAnnotation)) return false; if(tier > Tier::Op && layerCreator(msg.layer()) != msg.contextId()) { qWarning("non-op user %d tried to create annotation with context id %d", msg.contextId(), layerCreator(msg.layer())); return false; } break; case MSG_ANNOTATION_EDIT: // Non-operators can't edit protected annotations created by other users if(m_protectedAnnotations.contains(layerCreator(msg.layer())) && tier > Tier::Op && layerCreator(msg.layer()) != msg.contextId()) return false; if((static_cast<const AnnotationEdit&>(msg).flags() & protocol::AnnotationEdit::FLAG_PROTECT)) m_protectedAnnotations.insert(msg.layer()); else m_protectedAnnotations.remove(msg.layer()); break; case MSG_ANNOTATION_DELETE: case MSG_ANNOTATION_RESHAPE: if(m_protectedAnnotations.contains(msg.layer()) && tier > Tier::Op && layerCreator(msg.layer())!=msg.contextId()) return false; if(msg.type() == MSG_ANNOTATION_DELETE) m_protectedAnnotations.remove(msg.layer()); break; case MSG_UNDO: if(tier > featureTier(Feature::Undo)) return false; // Only operators can override Undos. if(tier > Tier::Op && static_cast<const Undo&>(msg).overrideId()>0) return false; break; default: break; } return true; }
void IndexBuilder::addToIndex(const protocol::Message &msg) { IndexType type = IDX_NULL; switch(msg.type()) { using namespace protocol; case MSG_CANVAS_RESIZE: type = IDX_RESIZE; break; case MSG_LAYER_CREATE: case MSG_LAYER_COPY: type = IDX_CREATELAYER; break; case MSG_LAYER_DELETE: type = IDX_DELETELAYER; break; case MSG_PUTIMAGE: type = IDX_PUTIMAGE; break; case MSG_PEN_MOVE: case MSG_PEN_UP: type = IDX_STROKE; break; case MSG_TOOLCHANGE: _colors[msg.contextId()] = static_cast<const protocol::ToolChange&>(msg).color_h(); break; case MSG_ANNOTATION_CREATE: case MSG_ANNOTATION_DELETE: case MSG_ANNOTATION_EDIT: case MSG_ANNOTATION_RESHAPE: type = IDX_ANNOTATE; break; case MSG_CHAT: type = IDX_CHAT; break; case MSG_INTERVAL: type = IDX_PAUSE; break; case MSG_MOVEPOINTER: type = IDX_LASER; break; case MSG_MARKER: type = IDX_MARKER; break; case MSG_USER_JOIN: _index._ctxnames[msg.contextId()] = static_cast<const protocol::UserJoin&>(msg).name(); return; default: break; } if(type==IDX_NULL) { return; } else if(type==IDX_PUTIMAGE || type==IDX_ANNOTATE) { // Combine consecutive messages from the same user for(int i=_index._index.size()-1;i>=0;--i) { IndexEntry &e = _index._index[i]; if(e.context_id == msg.contextId()) { if(e.type == type) { e.end = _pos; return; } break; } } } else if(type==IDX_LASER) { // Combine laser pointer strokes and drop other MovePointer messages for(int i=_index._index.size()-1;i>=0;--i) { IndexEntry &e = _index._index[i]; if(e.context_id == msg.contextId()) { if(e.type == type) { int persistence = static_cast<const protocol::MovePointer&>(msg).persistence(); if(persistence==0) { e._finished = true; return; } else if(!e._finished) { e.end = _pos; return; } } break; } } } else if(type==IDX_STROKE) { // Combine all strokes up to last pen-up from the same user for(int i=_index._index.size()-1;i>=0;--i) { IndexEntry &e = _index._index[i]; if(e.context_id == msg.contextId() && e.type == IDX_STROKE) { if(!e._finished) { e.end = _pos; if(msg.type() == protocol::MSG_PEN_UP) e._finished = true; return; } break; } } } // New index entry _index._index.append(IndexEntry(type, msg.contextId(), _offset, _pos, _pos, _colors[msg.contextId()])); }