Skip to content

系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI) #2

Description

@littlepure

[Bug] 系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI)

摘要

当用户选择"系统日历"或"两者都用 (Both)"作为数据源时,WinCal 会显示一批完全不存在的虚构日程(标题如"客户电话"、"代码审查"、"团队周会"、"产品评审",日历标为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A")。

这些数据并非来自用户真实的系统日历,而是 MockCalendarService 生成的假数据。根本原因有两个:

  1. BugCalendarViewModel.CreateSystemService() 在拿不到 WindowsCalendarService 类型时,错误地回退到了 MockCalendarService(本该回退到空的 EmptyCalendarService)。
  2. 功能缺失:WinCal 只读取 WinRT 的 Windows.ApplicationModel.Appointments(即 Windows 自带"邮件和日历"App 的日历仓库),不支持 Outlook classic(Exchange/MAPI,数据存于 .ost 文件)。大量用户(尤其是企业用户)的真实日程在 Outlook classic 里,WinCal 完全读不到。

两者叠加,造成"用户有真实日程(在 Outlook classic 里)却读不到,反而显示一堆虚构日程"的荒谬现象。

环境

  • WinCal 版本:1.3(scavin/WinCal main 分支,commit 1346dcc
  • 操作系统:Windows 11
  • 账户情况:仅使用 Outlook classic 连接公司 Exchange 账户(@corp.example.com),未在 Windows"邮件和日历"App 中添加任何账户

复现步骤

  1. 安装并启动 WinCal。
  2. 打开设置,将"日历数据源"设为 "系统日历""两者都用 (Both)"
  3. 观察日历面板中的事件列表 / 日期圆点。

实际结果:出现一批随机、不存在的日程("客户电话"、"代码审查"、"季度总结"、"1:1 会议"等),且日历来源显示为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A"。

预期结果:要么显示空的、要么显示用户真正的系统日历日程,绝不应显示随机生成的假日程。

根因分析

1. MockCalendarService 兜底 bug(显示虚假日程的直接原因)

Core/Services/MockCalendarService.cs 中的标题、日历名、地点为:

var titles = new[] {
    "团队周会", "产品评审", "季度总结", "1:1 会议", "午餐",
    "客户电话", "代码审查", "设计讨论", "项目排期", "培训"
};
var calendars = new[] { "Outlook", "Google Calendar", "iCloud" };
// ...
Location: rng.Next(3) < 1 ? "会议室 A" : "",

这些字面量与用户报告的"虚构日程"一一对应

ViewModels/CalendarViewModel.csCreateSystemService() 中:

private static ICalendarService CreateSystemService()
{
    try
    {
        var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
        if (type != null)
            return (ICalendarService)Activator.CreateInstance(type)!;
    }
    catch { } // WinRT not available
    return new MockCalendarService();   // ← 错误:生产环境兜底不应返回 Mock
}

WindowsCalendarService 类型反射不到(或初始化抛出异常)时,应返回 EmptyCalendarService(空数据),却返回了 MockCalendarService(随机假数据)MockCalendarService 明确标注为"开发调试用",不应出现在生产代码路径中。

2. 不支持 Outlook classic(真实日程读不到的原因)

WindowsCalendarService 通过 AppointmentManager.RequestStoreAsync(...) 读取的是 WinRT AppointmentStore,即 Windows 自带"邮件和日历"App 的数据仓库。

而 Outlook classic 的日程数据存储于 .ost(MAPI/Exchange)文件,属于另一套完全独立、互不同步的仓库。因此:

  • 用户真实日程(Outlook classic / Exchange)→ 存在于 .ost → WinCal 读不到
  • WinCal 能读到的 AppointmentStore → 可能为空 → 触发上面的 Mock 兜底 → 显示假数据。

修复建议

针对 Bug 1(必须修复)

CreateSystemService() 的兜底返回从 MockCalendarService 改为 EmptyCalendarService

private static ICalendarService CreateSystemService()
{
    try
    {
        var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
        if (type != null)
            return (ICalendarService)Activator.CreateInstance(type)!;
    }
    catch { }
    return new EmptyCalendarService();   // 修正:拿不到系统日历时返回空,而非造假数据
}

同时建议:MockCalendarService 应仅通过编译开关(如 #if DEBUG)或显式配置启用,避免任何生产环境误用。

针对功能缺失 2(增强请求)

考虑增加对 Outlook classic 日历的读取支持,可通过以下方式之一:

  • Outlook COM Interop:通过 Outlook.Application + Namespace.GetDefaultFolder(olFolderCalendar) 读取用户 Exchange/本地日历。
  • MAPI(如 MFCMAPI / Redemption 库)直接读取 .ost 中的日历项。
  • 或者,在设置界面提供明确的提示:"当前系统日历('邮件和日历'App)为空;若你的日程在 Outlook classic 中,请使用 ICS 订阅或将其账户添加到 Windows'邮件和日历'App"。

至少应在系统日历为空/不可用且未配置任何数据源时,向用户明确提示原因,而不是静默显示 Mock 假数据。

补充证据

通过 PowerShell 调用相同的 WinRT API 可复现:系统 AppointmentStore 中仅有一个空日历(SourceDisplayName=Microsoft 账户LocalId=b,5,13),近期无任何日程;而用户机器上存在多个 Exchange .ost 文件(真实日程所在),确认两套数据互不相通。


相关代码位置:

  • Core/Services/MockCalendarService.cs
  • Core/Services/EmptyCalendarService.cs
  • ViewModels/CalendarViewModel.csCreateSystemService
  • Core/Services/WindowsCalendarService.cs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions